package com.baidb.gateway.reactive.loadbalancer;

import com.baidb.gateway.properties.GrayProperties;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.consul.discovery.ConsulServer;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;

/**
 * @author baidb
 * @date 2021/6/17.
 */
public class GrayReactiveRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final AtomicInteger position;
    private final String serviceId;
    private GrayProperties grayProperties;

    public GrayReactiveRoundRobinLoadBalancer(
        ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
        String serviceId, GrayProperties grayProperties) {
        this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000), grayProperties);
    }

    public GrayReactiveRoundRobinLoadBalancer(
        ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
        String serviceId, int seedPosition, GrayProperties grayProperties) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(seedPosition);
        this.grayProperties = grayProperties;
    }

    @SuppressWarnings({"rawtypes", "deprecation"})
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        HttpHeaders headers = (HttpHeaders)request.getContext();
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
            .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get().next()
            .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, headers));
    }

    @SuppressWarnings("deprecation")
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
        List<ServiceInstance> serviceInstances, HttpHeaders headers) {
        Response<ServiceInstance> serviceInstanceResponse;
        if (grayProperties.isEnableGrayRelease()) {
            // 实现想要的路由规则逻辑
            String version = headers.getFirst("version");
            List<ServiceInstance> availableServiceInstances = serviceInstances.stream()
                .filter(server -> Objects.equals(server.getMetadata().get("version"), version))
                .collect(Collectors.toList());
            serviceInstanceResponse = getInstanceResponse(availableServiceInstances);
        } else {
            serviceInstanceResponse = getInstanceResponse(serviceInstances);
        }
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    @SuppressWarnings("deprecation")
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }

}
